#include <fuse.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stddef.h>
#include <sys/stat.h>
#include <plist/plist.h>


struct sparsefs_config {
    uint64_t bandSize;
    uint64_t diskSize;
    char *bundle_path;
};

struct sparsefs_config config;

    
static const char *device_path = "/device";

int	parse_sparsebundle_info(const char *bundlepath, uint64_t *band_size, uint64_t *disk_size);


static int sparse_getattr(const char *path, struct stat *stbuf)
{
    int res = 0;

    memset(stbuf, 0, sizeof(struct stat));
    if(strcmp(path, "/") == 0) {
        stbuf->st_mode = S_IFDIR | 0755;
        stbuf->st_nlink = 2;
    }
    else if(strcmp(path, device_path) == 0) {
        stbuf->st_mode = S_IFREG | 0444;
        stbuf->st_nlink = 1;
        stbuf->st_size = config.diskSize;
    }
    else
        res = -ENOENT;

    return res;
}

static int sparse_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
                          off_t offset, struct fuse_file_info *fi)
{
    (void) offset;
    (void) fi;

    if(strcmp(path, "/") != 0)
        return -ENOENT;

    filler(buf, ".", NULL, 0);
    filler(buf, "..", NULL, 0);
    filler(buf, device_path + 1, NULL, 0);

    return 0;
}

static int sparse_open(const char *path, struct fuse_file_info *fi)
{
    if(strcmp(path, device_path) != 0)
        return -ENOENT;

    if((fi->flags & 3) != O_RDONLY)
        return -EACCES;

    return 0;
}

static int sparse_read(const char *path, char *buf, size_t size, off_t offset,
                       struct fuse_file_info *fi)
{
    size_t len;
    off_t off;
    int amount, buf_offset = 0;


    (void) fi;
    if(strcmp(path, device_path) != 0)
        return -ENOENT;

    for (off = offset; off < (offset + size); off += amount)
    {
        int fileno = (off / config.bandSize), fd;
        off_t fileoff = (off % config.bandSize);
        char filename[256];

        snprintf(filename, 255, "%s/bands/%x", config.bundle_path, fileno);
        amount = (config.bandSize - fileoff);
        if (amount > (size - buf_offset))
            amount = (size - buf_offset);

        fd = open(filename, O_RDONLY);
        if (fd == -1)
            printf("Could not open file %s\r\n", filename);
        else {
            if (lseek(fd, fileoff, SEEK_SET) != fileoff)
                printf("Could not seek in file %s\r\n", filename);
            else {
                read(fd, (buf + buf_offset), amount);
            }

            close(fd);
            buf_offset += amount;
        }
    }

    return size;
}


static struct fuse_operations sparse_oper = {
    .getattr	= sparse_getattr,
    .readdir	= sparse_readdir,
    .open	= sparse_open,
    .read	= sparse_read,
};


static struct fuse_opt sparsefs_opts[] = {
	{ "-b %s",	offsetof(struct sparsefs_config, bundle_path), 0 },
	FUSE_OPT_END
};


int main(int argc, char *argv[])
{
    struct fuse_args args = FUSE_ARGS_INIT(argc, argv);


    memset(&config, 0, sizeof(config));
    fuse_opt_parse(&args, &config, sparsefs_opts, NULL);
    if (parse_sparsebundle_info(config.bundle_path, &config.bandSize, &config.diskSize) == -1)
    {
        printf("Could not parse sparse bundle info.\r\n");

        return -1;
    }

    return fuse_main(args.argc, args.argv, &sparse_oper);
}



int	parse_sparsebundle_info(const char *bundlepath, uint64_t *band_size, uint64_t *disk_size)
{
    char	infopath[256], *xml;
    struct stat filestats;
    plist_t	root = NULL, value;
    FILE	*infile = NULL;
    int		filesize;


    //
    // Open the Info.plist of the bundle.
    //
    snprintf(infopath, sizeof(infopath), "%s/Info.plist", bundlepath);
    infile = fopen(infopath, "rb");
    if (infile == NULL)
        return -1;

    //
    // Read in all the data about the file.
    //
    stat(infopath, &filestats);
    filesize = filestats.st_size;
    xml = (char *)malloc(sizeof(char *) * (filesize + 1));
    filesize = fread(xml, sizeof(char), filesize, infile);
    fclose(infile);

    //
    // Parse to a property list.
    //
    plist_from_xml(xml, filesize, &root);
    free(xml);
    if (root == NULL)
        return -1;

    //
    // Get the band size from the dictionary.
    //
    value = plist_dict_get_item(root, "band-size");
    if (value == NULL)
    {
        plist_free(root);
        return -1;
    }
    plist_get_uint_val(value, band_size);

    //
    // Get the disk size from the dictionary.
    //
    value = plist_dict_get_item(root, "size");
    if (value == NULL)
    {
        plist_free(root);
        return -1;
    }
    plist_get_uint_val(value, disk_size);

    plist_free(root);

    return 0;
}

